package net.ufrog.common.data.jpa;

import net.ufrog.common.Logger;
import net.ufrog.common.data.DataConfig;
import net.ufrog.common.data.QueryScript;
import net.ufrog.common.exception.ServiceException;
import net.ufrog.common.jetbrick.Templates;
import net.ufrog.common.utils.Files;
import net.ufrog.common.utils.Objects;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.io.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author ultrafrog, ufrog.net@gmail.com
 * @version 0.1, 2017-01-27
 * @since 0.1
 */
public class Jpql {

    /**  */
    @PersistenceContext
    private EntityManager em;

    /**
     * @param jpql 查询脚本
     * @param arguments 参数
     * @param requiredType 请求类型
     * @param <T> 请求泛型
     * @return 结果对象<br>若未查到结果返回NULL
     */
    public <T extends Serializable> T findSingle(String jpql, Map<String, Object> arguments, Class<T> requiredType) {
        QueryScript qs = new QueryScript(jpql);
        qs.append("", arguments);
        return findSingleByQueryScript(qs, requiredType);
    }

    /**
     * @param jpql 查询脚本
     * @param requiredType 请求类型
     * @param arguments 参数
     * @param <T> 请求泛型
     * @return 结果对象<br>若未查到结果返回NULL
     */
    public <T extends Serializable> T findSingle(String jpql, Class<T> requiredType, Object... arguments) {
        return findSingle(jpql, Objects.map(arguments), requiredType);
    }

    /**
     * @param queryScript 查询脚本对象
     * @param requiredType 请求类型
     * @param <T> 请求泛型
     * @return 结果对象<br>若未查到结果返回NULL
     */
    public <T extends Serializable> T findSingleByQueryScript(QueryScript queryScript, Class<T> requiredType) {
        TypedQuery<T> query = createTypedQuery(queryScript.getScriptWithoutOrder(), queryScript.getArguments(), null, requiredType);
        return query.getSingleResult();
    }

    /**
     * @param template 模版文件
     * @param arguments 参数
     * @param requiredType 请求类型
     * @param <T> 请求泛型
     * @return 结果对象
     */
    public <T extends Serializable> T findSingleByTemplate(String template, Map<String, Object> arguments, Class<T> requiredType) {
        return findSingle(rendTemplate(template, arguments), arguments, requiredType);
    }

    /**
     * @param template 模版文件
     * @param requiredType 请求类型
     * @param arguments 参数
     * @param <T> 请求泛型
     * @return 结果对象
     */
    public <T extends Serializable> T findSingleByTemplate(String template, Class<T> requiredType, Object... arguments) {
        return findSingleByTemplate(template, Objects.map(arguments), requiredType);
    }

    /**
     * @param sql 原生查询脚本
     * @param arguments 参数
     * @return 对象数组
     */
    public Object[] findSingleByNative(String sql, Map<String, Object> arguments) {
        QueryScript qs = new QueryScript(sql);
        qs.append("", arguments);
        return findSingleByNativeQueryScript(qs);
    }

    /**
     * @param sql 原生查询脚本
     * @param arguments 参数
     * @return 对象数组
     */
    public Object[] findSingleByNative(String sql, Object... arguments) {
        return findSingleByNative(sql, Objects.map(arguments));
    }

    /**
     * @param queryScript 查询脚本对象
     * @return 对象数组
     */
    public Object[] findSingleByNativeQueryScript(QueryScript queryScript) {
        Query query = createNativeQuery(queryScript.getScriptWithoutOrder(), queryScript.getArguments(), null);
        return (Object[]) query.getSingleResult();
    }

    /**
     * @param template 模版文件
     * @param arguments 参数
     * @return 对象数组
     */
    public Object[] findSingleByNativeTemplate(String template, Map<String, Object> arguments) {
        return findSingleByNative(rendTemplate(template, arguments), arguments);
    }

    /**
     * @param template 模版文件
     * @param arguments 参数
     * @return 对象数组
     */
    public Object[] findSingleByNativeTemplate(String template, Object... arguments) {
        return findSingleByNativeTemplate(template, Objects.map(arguments));
    }

    /**
     * @param jpql 查询脚本
     * @param arguments 参数
     * @param requiredType 请求类型
     * @param <T> 请求泛型
     * @return 结果列表<br>若未查到结果返回空列表
     */
    public <T extends Serializable> List<T> find(String jpql, Map<String, Object> arguments, Class<T> requiredType) {
        QueryScript qs = new QueryScript(jpql);
        qs.append("", arguments);
        return findByQueryScript(qs, requiredType);
    }

    /**
     * @param jpql 查询脚本
     * @param requiredType 请求类型
     * @param arguments 参数
     * @param <T> 请求泛型
     * @return 结果列表<br>若未查到结果返回空列表
     */
    public <T extends Serializable> List<T> find(String jpql, Class<T> requiredType, Object... arguments) {
        return find(jpql, Objects.map(arguments), requiredType);
    }

    /**
     * @param queryScript 查询脚本
     * @param requiredType 请求类型
     * @param <T> 请求泛型
     * @return 结果列表<br>若未查到结果返回空列表
     */
    public <T extends Serializable> List<T> findByQueryScript(QueryScript queryScript, Class<T> requiredType) {
        TypedQuery<T> query = createTypedQuery(queryScript.getScript(), queryScript.getArguments(), null, requiredType);
        return query.getResultList();
    }

    /**
     * @param template 模版文件
     * @param arguments 参数
     * @param requiredType 请求类型
     * @param <T> 请求泛型
     * @return 结果列表
     */
    public <T extends Serializable> List<T> findByTemplate(String template, Map<String, Object> arguments, Class<T> requiredType) {
        return find(rendTemplate(template, arguments), arguments, requiredType);
    }

    /**
     * @param template 模版文件
     * @param requiredType 请求类型
     * @param arguments 参数
     * @param <T> 请求泛型
     * @return 结果列表
     */
    public <T extends Serializable> List<T> findByTemplate(String template, Class<T> requiredType, Object... arguments) {
        return findByTemplate(template, Objects.map(arguments), requiredType);
    }

    /**
     * @param sql 原生查询脚本
     * @param arguments 参数
     * @return 结果列表
     */
    public List<Object[]> findByNative(String sql, Map<String, Object> arguments) {
        QueryScript qs = new QueryScript(sql);
        qs.append("", arguments);
        return findByNativeQueryScript(qs);
    }

    /**
     * @param sql 原生查询脚本
     * @param arguments 参数
     * @return 结果列表
     */
    public List<Object[]> findByNative(String sql, Object... arguments) {
        return findByNative(sql, Objects.map(arguments));
    }

    /**
     * @param queryScript 查询脚本对象
     * @return 结果列表
     */
    @SuppressWarnings("unchecked")
    public List<Object[]> findByNativeQueryScript(QueryScript queryScript) {
        Query query = createNativeQuery(queryScript.getScript(), queryScript.getArguments(), null);
        return query.getResultList();
    }

    /**
     * @param template 模版文件
     * @param arguments 参数
     * @return 结果列表
     */
    public List<Object[]> findByNativeTemplate(String template, Map<String, Object> arguments) {
        return findByNative(rendTemplate(template, arguments), arguments);
    }

    /**
     * @param template 模版文件
     * @param arguments 参数
     * @return 结果列表
     */
    public List<Object[]> findByNativeTemplate(String template, Object... arguments) {
        return findByNativeTemplate(template, Objects.map(arguments));
    }

    /**
     * @param jpql 查询脚本
     * @param arguments 参数
     * @param pageable 分页信息
     * @param requiredType 请求类型
     * @param <T> 请求泛型
     * @return 分页结果
     */
    public <T extends Serializable> Page<T> find(String jpql, Map<String, Object> arguments, Pageable pageable, Class<T> requiredType) {
        QueryScript qs = new QueryScript(jpql);
        qs.append("", arguments);
        if (!qs.hasOrder()) {
            qs.order(pageable.getSort());
        }
        return findByQueryScript(qs, pageable, requiredType);
    }

    /**
     * @param jpql 查询脚本
     * @param pageable 分页信息
     * @param requiredType 请求类型
     * @param arguments 参数
     * @param <T> 请求泛型
     * @return 分页结果
     */
    public <T extends Serializable> Page<T> find(String jpql, Pageable pageable, Class<T> requiredType, Object... arguments) {
        return find(jpql, Objects.map(arguments), pageable, requiredType);
    }

    /**
     * @param queryScript 查询脚本
     * @param pageable 分页信息
     * @param requiredType 请求类型
     * @param <T> 请求泛型
     * @return 分页结果
     */
    public <T extends Serializable> Page<T> findByQueryScript(QueryScript queryScript, Pageable pageable, Class<T> requiredType) {
        Long count = findSingle(queryScript.getCountScript(), queryScript.getArguments(), Long.class);
        List<T> list = Collections.emptyList();
        if (count.compareTo(0L) > 0) {
            TypedQuery<T> query = createTypedQuery(queryScript.getScript(), queryScript.getArguments(), pageable, requiredType);
            list = query.getResultList();
        }
        return new PageImpl<>(list, pageable, count);
    }

    /**
     * @param template 模版文件
     * @param arguments 参数
     * @param pageable 分页信息
     * @param requiredType 请求类型
     * @param <T> 请求泛型
     * @return 分页结果
     */
    public <T extends Serializable> Page<T> findByTemplate(String template, Map<String, Object> arguments, Pageable pageable, Class<T> requiredType) {
        return find(rendTemplate(template, arguments), arguments, pageable, requiredType);
    }

    /**
     * @param template 模版文件
     * @param pageable 分页信息
     * @param requiredType 请求类型
     * @param arguments 参数
     * @param <T> 请求泛型
     * @return 分页结果
     */
    public <T extends Serializable> Page<T> findByTemplate(String template, Pageable pageable, Class<T> requiredType, Object... arguments) {
        return findByTemplate(template, Objects.map(arguments), pageable, requiredType);
    }

    /**
     * @param sql 原生查询脚本
     * @param arguments 参数
     * @param pageable 分页信息
     * @return 分页结果
     */
    public Page<Object[]> findByNative(String sql, Map<String, Object> arguments, Pageable pageable) {
        QueryScript qs = new QueryScript(sql);
        qs.append("", arguments);
        if (!qs.hasOrder()) {
            qs.order(pageable.getSort());
        }
        return findByNativeQueryScript(qs, pageable);
    }

    /**
     * @param sql 原生查询脚本
     * @param pageable 分页信息
     * @param arguments 参数
     * @return 分页结果
     */
    public Page<Object[]> findByNative(String sql, Pageable pageable, Object arguments) {
        return findByNative(sql, Objects.map(arguments), pageable);
    }

    /**
     * @param queryScript 查询脚本对象
     * @param pageable 分页信息
     * @return 分页结果
     */
    @SuppressWarnings("unchecked")
    public Page<Object[]> findByNativeQueryScript(QueryScript queryScript, Pageable pageable) {
        Long count = (Long) findSingleByNative(queryScript.getCountScript(), queryScript.getArguments())[0];
        List<Object[]> list = Collections.emptyList();
        if (count.compareTo(0L) > 0) {
            Query query = createNativeQuery(queryScript.getScript(), queryScript.getArguments(), pageable);
            list = query.getResultList();
        }
        return new PageImpl<>(list, pageable, count);
    }

    /**
     * @param template 模版文件
     * @param arguments 参数
     * @param pageable 分页信息
     * @return 分页结果
     */
    public Page<Object[]> findByNativeTemplate(String template, Map<String, Object> arguments, Pageable pageable) {
        return findByNative(rendTemplate(template, arguments), arguments, pageable);
    }

    /**
     * @param template 模版文件
     * @param pageable 分页信息
     * @param arguments 参数
     * @return 分页结果
     */
    public Page<Object[]> findByNativeTemplate(String template, Pageable pageable, Object... arguments) {
        return findByNativeTemplate(template, Objects.map(arguments), pageable);
    }

    /**
     * @param jpql 查询脚本
     * @param arguments 参数
     * @return 处理数据量
     */
    public Integer execute(String jpql, Map<String, Object> arguments) {
        Query query = createQuery(jpql, arguments, null);
        return query.executeUpdate();
    }

    /**
     * @param jpql 查询脚本
     * @param arguments 参数
     * @return 处理数据量
     */
    public Integer execute(String jpql, Object... arguments) {
        return execute(jpql, Objects.map(arguments));
    }

    /**
     * @return entity manager
     */
    public EntityManager getEntityManager() {
        return em;
    }

    /**  */
    public void clear() {
        getEntityManager().clear();
    }

    /**  */
    public void flush() {
        getEntityManager().flush();
    }

    /**  */
    public void close() {
        getEntityManager().close();
    }

    /**
     * @param template 模版文件
     */
    public void clearTemplate(String template) {
        Templates.clear(template);
    }

    /**  */
    public void clearAllTemplates() {
        Templates.clearAll();
    }

    /**
     * @param ql 查询脚本
     * @param arguments 参数
     * @param pageable 分页信息
     * @return 查询对象
     */
    public Query createQuery(String ql, Map<String, Object> arguments, Pageable pageable) {
        Query query = getEntityManager().createQuery(ql);
        setArguments(query, arguments, pageable);
        return query;
    }

    /**
     * @param jpql 查询脚本
     * @param arguments 参数
     * @param pageable 分页信息
     * @param requiredType 请求类型
     * @param <T> 类型泛型
     * @return 查询对象
     */
    public <T extends Serializable> TypedQuery<T> createTypedQuery(String jpql, Map<String, Object> arguments, Pageable pageable, Class<T> requiredType) {
        TypedQuery<T> query = getEntityManager().createQuery(jpql, requiredType);
        setArguments(query, arguments, pageable);
        return query;
    }

    /**
     * @param sql 原生查询脚本
     * @param arguments 参数
     * @param pageable 分页信息
     * @return 查询对象
     */
    public Query createNativeQuery(String sql, Map<String, Object> arguments, Pageable pageable) {
        Query query = getEntityManager().createNativeQuery(parseSql(sql));
        setArguments(query, arguments, pageable);
        return query;
    }

    /**
     * @param query 查询对象
     * @param arguments 参数
     * @param pageable 分页信息
     */
    public void setArguments(Query query, Map<String, Object> arguments, Pageable pageable) {
        arguments.forEach(query::setParameter);
        if (pageable != null) {
            query.setFirstResult(pageable.getOffset());
            query.setMaxResults(pageable.getPageSize());
        }
    }

    /**
     * @param template 模版文件
     * @return 查询脚本
     */
    public String rendTemplate(String template, Map<String, Object> arguments) {
        String source = null;
        if (!Templates.isCached(template)) {
            File file = Files.getFile(DataConfig.getDBTemplate() + template + ".ql");
            try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
                source = parseSql(reader.lines().map(String::trim).collect(Collectors.joining(" ")));
                Logger.info("read template '%s': %s", template, source);
            } catch (IOException e) {
                throw new ServiceException(e.getMessage(), e);
            }
        }
        return Templates.render(template, source, arguments);
    }

    /**
     * @param sql 原生查询脚本
     * @return 处理后脚本
     */
    public String parseSql(String sql) {
        return sql.replaceAll("@", DataConfig.getDBPrefix());
    }
}
